<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset='utf-8'> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <meta name="google" content="notranslate">
    <title>folder listing: /Demo.Web/</title>
    <style>
body {
  font-family: monospace;
  font-size: large;
}
h1 {
    font-size: large;
}
:hover {
  cursor: pointer;
}
p {
  line-height: 1.5;
}
a {
  text-decoration: none;
}
#stacktrace {
  margin-top: 15px;
}
.directory h1 {
  margin-bottom: 15px;
}
.sort-up::after {
  content: "▲";
}
.sort-down::after {
  content: "▼";
}
.sort-down,
.sort-up {
  color: seagreen;
}
table#files {
    padding: 0;
    width: 100%;
    table-layout: fixed;
}
table#files tr {
  
}
table#files tr:nth-child(odd) {
    background: #EEE;
}
table#files tr {
  border: 1px solid transparent;
  border-radius: 5px;
  overflow: hidden;
}
/* note: these are n, m, s because Kodi is hardcoded to look for these >:( */
/* name */
table#files td.i,
table#files th.i {
  font-size: medium;
  text-align: center;
  vertical-align: middle;  
  width: 1em;
}
table#files td.n {
}
table#files td.n a {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* date (modified) */
/* the align=right is required by kodi (T_T) */
table#files td.m,
table#files th.m,
table#files td[align=right] {
  text-align: center !important;
  white-space: nowrap;
  width: 11em;
}
/* size */
table#files td.s,
table#files th.s {
  text-align: right;
  width: 4em;
}

a:hover {
  background: #DDE;
}
table#files tr:focus,
table#files tr:hover {
  background: #DDE;
  border: 1px solid #AAA;
  cursor: pointer;
}
table#files tr.highlight {
  display: none;
}
#search {
  display: block;
  position: fixed;
  top: 1em;
  right: 1em;
  width: 15em;
  text-align: left;
  padding: .25em .5em;
  margin-bottom: 0;
  outline: none;
  border: 1px solid #ddd;
  background: rgba(255, 255, 255, 0.9);
}

@media (max-width: 600px) {
  #files td.s,
  #files th.s,
  #files th.m,
  table#files td[align=right] {
    display: none;
  }
}

@media (prefers-color-scheme: dark) {
  html {
    scrollbar-color: hsl(0, 0%, 35%) hsl(0, 0%, 13%);
  }
  body {
    background: #333;
    color: #CCC;
  }
  input {
    color: #CCC;
  }
  table#files tr:nth-child(odd) {
    background: #444;
  }
  table#files tr a {
    color: #ACF;
  }
  .sort-down,
  .sort-up {
    color: lightseagreen;
  }
  a {
    color: #8AC;
  }
  a:visited {
    color: #ACF;
  }
  a:hover {
    background: #556;
  }
  table#files tr:focus,
  table#files tr:hover {
    background: #556;
    border: 1px solid #333;
  }
  #search {
    border: 1px solid #ddd;
    background: rgba(0, 0, 0, 0.9);
  }
}
    </style>
  </head>
  <body class="directory">
    <input id="search" type="text" placeholder="Search" autocomplete="off" />
    <div id="wrapper">
      <h1><a href="/">~</a> / <a href="/Demo.Web">Demo.Web</a> / </h1>
      <table id="files">
<tr>        <td class="i"></td>        <td class="n"><a href="/Demo.Web/A1.Demo3D_Well%26horizons.html">A1.Demo3D_Well&amp;horizons.html</a></td>        <td align="right">2025-08-14 09:34 </td>        <td class="s">22.9K</td>      </tr>
<tr>        <td class="i"></td>        <td class="n"><a href="/Demo.Web/A2.Demo3D_Model.html">A2.Demo3D_Model.html</a></td>        <td align="right">2025-08-14 09:54 </td>        <td class="s">14.0K</td>      </tr>
<tr>        <td class="i"></td>        <td class="n"><a href="/Demo.Web/A3.Demo3D_Grid_Zmap.html">A3.Demo3D_Grid_Zmap.html</a></td>        <td align="right">2025-08-14 09:57 </td>        <td class="s">16.0K</td>      </tr>
<tr>        <td class="i"></td>        <td class="n"><a href="/Demo.Web/A4.Demo3D_Grid_EarthVision.html">A4.Demo3D_Grid_EarthVision.html</a></td>        <td align="right">2025-08-14 09:59 </td>        <td class="s">14.6K</td>      </tr>
<tr>        <td class="i"></td>        <td class="n"><a href="/Demo.Web/A5.Demo3D_Seis3D.html">A5.Demo3D_Seis3D.html</a></td>        <td align="right">2025-08-14 09:59 </td>        <td class="s">8.6K</td>      </tr>
<tr>        <td class="i"></td>        <td class="n"><a href="/Demo.Web/A6.Demo3D_Animate_Tween.html">A6.Demo3D_Animate_Tween.html</a></td>        <td align="right">2025-08-14 10:45 </td>        <td class="s">11.5K</td>      </tr>
    </div>
    <script>
    {
      // this shouldn't happen here but our hand is forced
      // because we want Kodi to be able to parse the HTML
       document.querySelector('#files').addEventListener('click', function(e) {
         var tr = e.target.closest('tr');
         if (tr) {
           var a = tr.querySelector('a');
           if (a) {
             window.location.href = a.href;
           }
         }
       });
    }

    {
      function createElem(tag, attrs = {}) {
        const elem = document.createElement(tag);
        for (const [key, value] of Object.entries(attrs)) {
          if (typeof value === 'object') {
            for (const [k, v] of Object.entries(value)) {
              try {
              elem[key][k] = v;
              } catch (e) {
                debugger;  // eslint-disable-line no-debugger
              }
            }
          } else if (elem[key] === undefined) {
            elem.setAttribute(key, value);
          } else {
            elem[key] = value;
          }
        }
        return elem;
      }

      function addElem(tag, parent, attrs = {}) {
        const elem = createElem(tag, attrs);
        parent.appendChild(elem);
        return elem;
      }

      function byTextContent(columnNdx) {
        return (a, b) => {
          const av = a.cells[columnNdx].textContent.toLowerCase();
          const bv = b.cells[columnNdx].textContent.toLowerCase();
          return av === bv ? 0 : av < bv ? -1 : 1;
        };
      }

      function unFormatBytes(str) {
        const suffixes = {
          'b': 0,
          'k': 2**10,
          'm': 2**20,
          'g': 2**30,
        };
        const mult = suffixes[str.slice(-1).toLowerCase()];
        if (mult === undefined) {
          return -1;
        }
        return parseFloat(str) * mult;
      };

      function bySizeAbbreviation(columnNdx) {
        return (a, b) => {
          const av = unFormatBytes(a.cells[columnNdx].textContent);
          const bv = unFormatBytes(b.cells[columnNdx].textContent);
          return av === bv ? 0 : av < bv ? -1 : 1;
        };
      }

      function reverseSort(sortFn) {
        return (a, b) => sortFn(b, a);
      }

      const table = document.querySelector('#files');
      const thead = addElem('thead', table);
      const tr = addElem('tr', thead);
      const headings = [
        { attribs: {textContent: '',     className: 'i'}, sortFn: byTextContent, },
        { attribs: {textContent: 'name', className: 'n'}, sortFn: byTextContent, },
        { attribs: {textContent: 'date', className: 'm'}, sortFn: byTextContent, },
        { attribs: {textContent: 'size', className: 's'}, sortFn: bySizeAbbreviation, },
      ];
      const headingElements = headings.map(heading => {
        const elem = addElem('th', tr, heading.attribs);
        elem.addEventListener('click', (e) => {
          const reverse = elem.classList.contains('sort-down');
          headingElements.forEach(elem => {
            elem.classList.remove('sort-up');
            elem.classList.remove('sort-down');
          });
          const sortBy = heading.attribs.className;
          sort(sortBy, reverse);
        });
        return elem;
      });

      const sort = (sortBy, reverse) => {
        const thead = document.querySelector('#files thead>tr');
        const th = thead.querySelector(`th.${sortBy}`);
        th.classList.add(reverse ? 'sort-up' : 'sort-down');
        const url = new URL(window.location.href);
        url.hash = `sortBy=${sortBy}${reverse ? '&reverse=true' : ''}`
        history.replaceState(null, '', url.href);

        const columnNdx = [...thead.cells].indexOf(th);
        const tbody = document.querySelector('#files tbody');
        const rows = [...tbody.rows];
        rows.forEach(row => row.remove());
        const sortFn = headings[columnNdx].sortFn(columnNdx);
        rows.sort(reverse ? reverseSort(sortFn) : sortFn);
        rows.forEach(row => tbody.appendChild(row));
      };

      {
        const params = {};
        window.location.hash.substr(1).split('&').forEach(pair => {
          const kv = pair.split('=');
          params[kv[0]] = kv[1];
        });
        const sortBy = params.sortBy;
        if (sortBy) {
          sort(sortBy, !!params.reverse);
        }
      }
    }

    {
      var icons = {
        jpg: '🏙',
        jpeg: '🏙',
        png: '🌄',
        gif: '🌠',
        mp4: '🎬',
        m4v: '🎬',
        mov: '🎬',
        avi: '🎬',
        mkv: '🎬',
        wmv: '🎬',
        mp3: '🔈',
        ogg: '🔈',
        wav: '🔈',
        flac: '🔈',
        acc: '🔈',
        exe: '☠️',
      };
      var extRE = /\.[^/.]*$/;
      document.querySelector('#files').querySelectorAll('tbody>tr').forEach(function(row) {
        var icon = row.querySelector('.i');
        var a = row.querySelector('a');
        var m = extRE.exec(a.href);
        var ext = m ? m[0].substr(1).toLowerCase() : '';
        icon.textContent = icons[ext] || (a.href.endsWith('/') ? '📁' : '📄');
      });
    }


    {
      function $(id) {
        return document.getElementById(id);
      }

      function search() {
        var str = $('search').value.toLowerCase();
        var rows = document.querySelector('#files tbody').querySelectorAll('tr');

        rows.forEach(function(row){
          var link = row.querySelector('a');
          var text = row.textContent.toLowerCase();

          if ('..' == text) return;
          if (str.length && !~text.indexOf(str)) {
            row.classList.add('highlight');
          } else {
            row.classList.remove('highlight');
          }
        });
      }

      $('search').addEventListener('keyup', search);
    }
    </script>
  </body>
</html>
